"""
主题: 利用装饰器强制函数上的类型检查
问题: 作为某种编程规约，你想在对函数参数进行强制类型检查。
提示 : 
"""

from inspect import signature
from functools import wraps

def typeassert(*ty_args, **ty_kwargs):
    def decorate(func):
        # If in optimized mode, disable type checking
        if not __debug__:
            return func

        # Map function argument names to supplied types
        sig = signature(func)
        bound_types = sig.bind_partial(*ty_args, **ty_kwargs).arguments

        @wraps(func)
        def wrapper(*args, **kwargs):
            bound_values = sig.bind(*args, **kwargs)
            # Enforce type assertions across supplied arguments
            for name, value in bound_values.arguments.items():
                if name in bound_types:
                    if not isinstance(value, bound_types[name]):
                        raise TypeError(
                            'Argument {} must be {}'.format(name, bound_types[name])
                            )
            return func(*args, **kwargs)
        return wrapper
    return decorate

@typeassert(int, int)
def add(x, y):
    return x + y

@typeassert(int, z=int)
def spam(x, y, z=42):
    print(x, y, z)

@typeassert(int, list)
def bar(x, items=None):
    '''这个方案还有点小瑕疵，它对于有默认值的参数并不适用'''
    if items is None:
        items = []
    items.append(x)
    return items        

def recipe1():
    print(f"{add(1, 3) = }")
    
    try:
        add(1, '1')
    except TypeError as e:
        print(e)   

def recipe2():
    print(f"{spam(1, 'hello', 3) = }")

    try:
        spam('hello', 'world', 3)
    except TypeError as e:
        print(e) 

    try:
        spam(1, 'hello', 'world')
    except TypeError as e:
        print(e) 


def recipe3():
    print(f"{bar(2) = }")
    print(f"{bar(4, [1, 2, 3]) = }")

    try:
        bar(2,3)
    except TypeError as e:
        print(e) 
        

def main():
    print('recipe1'.center(20, '*'))
    recipe1()
    print('recipe2'.center(20, '*'))
    recipe2()
    print('recipe3'.center(20, '*'))
    recipe3()

if __name__ == '__main__':
    main()          